package com.lagou.rpc.provider.server;

import com.lagou.rpc.provider.handler.RpcServerHandler;
import com.lagou.rpc.provider.zkUtil.ZKClientUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import static com.lagou.rpc.provider.zkUtil.ZKClientUtil.Server_Port;

/**
 * 启动类
 *
 *  * 编程题一：
 *  *
 *  *   在基于Netty的自定义RPC的案例基础上，进行改造。基于Zookeeper实现简易版服务的注册与发现机制
 *  *
 *  * 要求完成改造版本：
 *  *
 *  * 1. 启动2个服务端，可以将IP及端口信息自动注册到Zookeeper
 *  *
 *  * 2. 客户端启动时，从Zookeeper中获取所有服务提供端节点信息，客户端与每一个服务端都建立连接
 *  *
 *  * 3. 某个服务端下线后，Zookeeper注册列表会自动剔除下线的服务端节点，客户端与下线的服务端断开连接
 *  *
 *  * 4. 服务端重新上线，客户端能感知到，并且与重新上线的服务端重新建立连接
 *  *
 *  * 编程题二：
 *  *
 *  *   在“编程题一”的基础上，实现基于Zookeeper的简易版负载均衡策略
 *  *
 *  * 要求完成改造版本：
 *  *
 *  * Zookeeper记录每个服务端的最后一次响应时间，有效时间为5秒，5s内如果该服务端没有新的请求，响应时间清零或失效。
 *  *
 *  * 当客户端发起调用，每次都选择最后一次响应时间短的服务端进行服务调用，如果时间一致，随机选取一个服务端进行调用，从而实现负载均衡
 *  *
 *  *
 *  *
 *  * 实现步骤：
 *  *
 *  * 服务端启动后链接zk，创建临时节点。调用spring容器关闭监听回调方法，结束zk的session，节点被删除
 *  *
 *  * 客服端启动去找zk下的服务节点，与每一个都连接
 *  *
 *  * 服务端节点删除或新增时，通知到客户端，断开或新增链接
 *  *
 *  * 客户端发起调用时先查服务端节点里保存的最后一次调用时间，取小的调用，若一致随机调用即可
 *
 */
@Service
public class RpcServer implements DisposableBean {

    private NioEventLoopGroup bossGroup;

    private NioEventLoopGroup workerGroup;

    @Autowired
    RpcServerHandler rpcServerHandler;

    public void startServer(String ip, int port) {
        try {
            //1. 创建线程组
            bossGroup = new NioEventLoopGroup(1);
            workerGroup = new NioEventLoopGroup();
            //2. 创建服务端启动助手
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //3. 设置参数
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            ChannelPipeline pipeline = channel.pipeline();
                            //添加String的编解码器
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            //业务处理类
                            pipeline.addLast(rpcServerHandler);
                        }
                    });
            //4.绑定端口
            ChannelFuture sync = serverBootstrap.bind(ip, Server_Port).sync();
            System.out.println("==========服务端启动成功==========");

            // 连接zk
            ZkClient zkClient = ZKClientUtil.getInstance();
            System.out.println("==========ZooKeeper session established.==========");
            String path = ZKClientUtil.pathPre + "/" + ZKClientUtil.Server_IP+"_"+ZKClientUtil.Server_Port;
            zkClient.createEphemeral(path,true);
            System.out.println("success create znode.");

            // 设置节点接口处理时间
            // 只存一个数字：最近一次接口处理时间。默认为0，这样节点刚新增时会被调用，而后会被刷新处理时间
            zkClient.writeData(path,0);

//            String path1 = ZKClientUtil.pathPre+ "/" + ZKClientUtil.Server_IP+"_"+ZKClientUtil.Server_Port+"12";
//            zkClient.createPersistent(path1,true);
//            String path2 = ZKClientUtil.pathPre+ "/" + ZKClientUtil.Server_IP+"_"+ZKClientUtil.Server_Port;
//            zkClient.createPersistent(path2,true);

//            Object o = zkClient.readData(path);

            sync.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {

            if (bossGroup != null) {
                bossGroup.shutdownGracefully();
            }

            if (workerGroup != null) {
                workerGroup.shutdownGracefully();
            }
        }
    }


    @Override
    public void destroy() throws Exception {
        if (bossGroup != null) {
            bossGroup.shutdownGracefully();
        }

        if (workerGroup != null) {
            workerGroup.shutdownGracefully();
        }
    }
}
